/*
 *  Copyright (C) 2011  Clint Priest
 *
 *  This file is part of pklib.
 *
 *  pklib is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  pklib is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with pklib.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

/*
 * Prototype Imports -- Mozilla Organization may not like these...
 */

/* Returns true if object is a function */
Object.isFunction = function isFunction(object) {
	return typeof object === 'function';
};

/* Extends the destination object with properties from the source object */
Object.extend = function extend(destination, source) {
	for (var property in source)
		destination[property] = source[property];
	return destination;
};


/* Returns an array of the argument names to the given function */
Function.argumentNames = function argumentNames(func) {
	var names = func.toString().match(/^[\s\(]*function[^(]*\(([^)]*)\)/)[1]
					.replace(/\/\/.*?[\r\n]|\/\*(?:.|[\r\n])*?\*\//g, '')
					.replace(/\s+/g, '').split(',');
	return names.length == 1 && !names[0] ? [] : names;
};

/** Returns the given func wrapped with wrapper */
Function.wrap = function wrap(func, wrapper) {
	/* Localized version of Function.update from prototype */
	function update(array, args) {
		var arrayLength = array.length, length = args.length;
		while (length--) array[arrayLength + length] = args[length];
		return array;
	}

	var __method = func;
	return function() {
		var a = update([__method.bind(this)], arguments);
		return wrapper.apply(this, a);
	};
};


/** Fully functioning prototype class inheritence, also allows for getters/setters including c# style getters/setters */
var Class = (function() {
	function subclass() {};

	function create() {

		var parent = null, properties = $A(arguments);
		if (Object.isFunction(properties[0]))
			parent = properties.shift();

		function klass() {
			this.initialize.apply(this, arguments);
		}
		klass.superclass = parent;
		klass.subclasses = [];

		klass.addMethods = Class.Methods.addMethods;

		if (parent) {
			subclass.prototype = parent.prototype;
			klass.prototype = new subclass;
			if(!parent.subclasses)
				parent.subclasses = [ ];
			parent.subclasses.push(klass);
		}

		for (var i = 0; i < arguments.length; i++)
			klass.addMethods(arguments[i]);

		if (!klass.prototype.initialize)
			klass.prototype.initialize = function() { };

		klass.prototype.constructor = klass;
		return klass;
	}

	function addMethods(source) {
		var ancestor   = this.superclass && this.superclass.prototype;
		var properties = Object.keys(source);

		for (var i = 0, length = properties.length; i < length; i++) {
			var property = properties[i];

			var PropertyDescriptor  = Object.getOwnPropertyDescriptor(source, property);
			
			if(PropertyDescriptor != undefined && PropertyDescriptor.value == undefined) {
				Object.defineProperty(this.prototype, property, PropertyDescriptor);
			} else {
//				var Setter = source.__lookupSetter__(property),
//					Getter = source.__lookupGetter__(property);

//				if(Setter)
//					this.prototype.__defineSetter__(property, Setter);
//				if(Getter)
//					this.prototype.__defineGetter__(property, Getter);

//				if(Setter == undefined && Getter == undefined) {
					/* Support Name: { get: function(), set: function() } syntax, ala C# getter/setter syntax */
					var value = source[property];
					if(value && typeof value == 'object' && (value.get || value.set)) {
						if(value.set)
							this.prototype.__defineSetter__(property, value.set);
						if(value.get)
							this.prototype.__defineGetter__(property, value.get);
					} else {
						if (ancestor && Object.isFunction(value) &&	Function.argumentNames(value)[0] == "$super") {
							var method = value;
							value = Function.wrap(
								(function(m) {
									return function() { return ancestor[m].apply(this, arguments); };
								})(property), method
							);

							value.valueOf = method.valueOf.bind(method);
							value.toString = method.toString.bind(method);
						}
						this.prototype[property] = value;
					}
//				}
			}
		}
		return this;
	}

	return {
		create: create,
		Methods: {
			addMethods: addMethods
		}
	};
})();
